
#include "bb_buffer.h"
#include "gkt_debug.h"
#include "gkt_os.h"
#include "gkt_crc8.h"
#include <string.h>

#if !BB_BUFFER_USE_SYSTEM

#define BB_BUFFER_ALIGNMENT	8
#define BB_BUFFER_ALIGNMASK	(BB_BUFFER_ALIGNMENT - 1)

#ifdef GKT_CONFIG_BB_BUFFER_MAXSIZE
#define BB_BUFFER_MAXSIZE	\
	((GKT_CONFIG_BB_BUFFER_MAXSIZE + BB_BUFFER_ALIGNMASK) & (~BB_BUFFER_ALIGNMASK))
#else
#define BB_BUFFER_MAXSIZE	0x400
#endif

#define BB_BUFFER_MCHUNK_MAGIC	0xBFE3C3AC

typedef struct _bb_buffer_mchunk_hdr bb_buffer_mchunk_hdr_s;
typedef struct _bb_buffer_mchunk_hdr bb_buffer_mchunk_s;
struct _bb_buffer_mchunk_hdr {
	uint32_t	magic;
	union {
		struct {
			uint32_t	prev_size : 15;	/* unit in bytes */
			uint32_t	reserved : 1;
			uint32_t	curr_size : 15;	/* unit in bytes */
			uint32_t	used : 1;
		};
		uint32_t u32_attr;
	};
};
#define BB_BUFFER_MCHUNK_HDRSIZE	sizeof(bb_buffer_mchunk_hdr_s)
#define BB_BUFFER_MCHUNK_MINSIZE	(BB_BUFFER_MCHUNK_HDRSIZE + BB_BUFFER_ALIGNMENT)

typedef union {
	int	init_ok;
	gkt_mutex_t	mutex;
	uint8_t	data[BB_BUFFER_MAXSIZE];
	uint32_t	u32_data[BB_BUFFER_MAXSIZE >> 2];
} bb_buffer_u;
static bb_buffer_u s_bb_buffer;

#define BB_BUFFER_MCHUNK_HEAD	\
	((bb_buffer_mchunk_s *)&s_bb_buffer.data[0])
#define BB_BUFFER_MCHUNK_END	\
	((bb_buffer_mchunk_s *)&s_bb_buffer.data[BB_BUFFER_MAXSIZE - BB_BUFFER_MCHUNK_MINSIZE])

#define BB_BUFFER_MCHUNK_PREV(_mchunk_)	\
	(bb_buffer_mchunk_s *)((uint8_t *)(_mchunk_) - (_mchunk_)->prev_size - BB_BUFFER_MCHUNK_HDRSIZE)
#define BB_BUFFER_MCHUNK_NEXT(_mchunk_)	\
	(bb_buffer_mchunk_s *)((uint8_t *)(_mchunk_) + BB_BUFFER_MCHUNK_HDRSIZE + (_mchunk_)->curr_size)

#define BB_BUFFER_MCHUNK_IS_VALID(_mchunk)	\
	((BB_BUFFER_MCHUNK_MAGIC == (_mchunk)->magic) \
		&& ((_mchunk)->curr_size >= BB_BUFFER_ALIGNMENT))

static void bb_buffer_update_next_mchunk(bb_buffer_mchunk_s *mchunk)
{
	bb_buffer_mchunk_s *next_mchunk;

	next_mchunk = BB_BUFFER_MCHUNK_NEXT(mchunk);
	if ((uint32_t)next_mchunk <= (uint32_t)BB_BUFFER_MCHUNK_END) {
		if (BB_BUFFER_MCHUNK_IS_VALID(next_mchunk))
			next_mchunk->prev_size = mchunk->curr_size;
		else {
			gkt_error("update_next: memory is polluted!\n");
		}
	}
}

static bb_buffer_mchunk_s *bb_buffer_merge_prev_free_mchunk(
			bb_buffer_mchunk_s *mchunk)
{
	bb_buffer_mchunk_s *prev_mchunk;

	if (mchunk->prev_size >= BB_BUFFER_ALIGNMENT) {
		prev_mchunk = BB_BUFFER_MCHUNK_PREV(mchunk);
		if (((uint32_t)prev_mchunk >= (uint32_t)BB_BUFFER_MCHUNK_HEAD)
			&& BB_BUFFER_MCHUNK_IS_VALID(prev_mchunk)) 
		{
			if (!prev_mchunk->used) {
				prev_mchunk->curr_size += BB_BUFFER_MCHUNK_HDRSIZE + mchunk->curr_size;
				mchunk->magic = 0;
				mchunk->u32_attr = 0;
				mchunk = prev_mchunk;
			}
		}
		else
			gkt_error("merge_prev: memory is polluted!\n");
	}

	return mchunk;
}

static void bb_buffer_merge_next_free_mchunk(bb_buffer_mchunk_s *mchunk)
{
	bb_buffer_mchunk_s *next_mchunk;

	next_mchunk = BB_BUFFER_MCHUNK_NEXT(mchunk);
	if ((uint32_t)next_mchunk <= (uint32_t)BB_BUFFER_MCHUNK_END) {
		if (BB_BUFFER_MCHUNK_IS_VALID(next_mchunk)) {
			if (next_mchunk->used) {
				mchunk->curr_size += BB_BUFFER_MCHUNK_HDRSIZE + next_mchunk->curr_size;
				next_mchunk->magic = 0;
				next_mchunk->u32_attr = 0;
			}
		}
		else
			gkt_error("merge_next: memory is polluted!\n");
	}
}

void bb_buffer_free(uint8_t *buffer)
{
	bb_buffer_mchunk_s *mchunk;

	if (!s_bb_buffer.init_ok)	/* not init completely */
		return;

	if (!((uint32_t)buffer & 0x03)
		&& (buffer >= &s_bb_buffer.data[BB_BUFFER_MCHUNK_HDRSIZE]) 
		&& (buffer <= &s_bb_buffer.data[BB_BUFFER_MAXSIZE - BB_BUFFER_ALIGNMENT]))
	{
		mchunk = (bb_buffer_mchunk_s *)(buffer - BB_BUFFER_MCHUNK_HDRSIZE);

		gkt_mutex_lock(s_bb_buffer.mutex, GKT_INFINITE);
		if (BB_BUFFER_MCHUNK_IS_VALID(mchunk)) {
			if (mchunk->used) {
				mchunk->used = 0;
				mchunk = bb_buffer_merge_prev_free_mchunk(mchunk);
				bb_buffer_merge_next_free_mchunk(mchunk);
				bb_buffer_update_next_mchunk(mchunk);
			}
			else
				gkt_error("bb_buffer_free: Repeative free - ptr(0x%08x)\n", (uint32_t)buffer);
		}
		else
			gkt_error("bb_buffer_free: Invalid mchunk - ptr(0x%08x)\n", (uint32_t)buffer);
		gkt_mutex_unlock(s_bb_buffer.mutex);
	}
	else
		gkt_error("bb_buffer_free: Out-Of-Range - ptr(0x%08x)\n", (uint32_t)buffer);
}

uint8_t *bb_buffer_alloc(uint32_t size)
{
	bb_buffer_mchunk_s *mchunk, *mchunk_suitable;
	uint32_t aligned_size;

	if (!s_bb_buffer.init_ok)	/* not init completely */
		return NULL;

	aligned_size = (size + BB_BUFFER_ALIGNMASK) & (~BB_BUFFER_ALIGNMASK);
	mchunk_suitable = NULL;
	mchunk = BB_BUFFER_MCHUNK_HEAD;

	gkt_mutex_lock(s_bb_buffer.mutex, GKT_INFINITE);
	while ((uint32_t)mchunk <= (uint32_t)BB_BUFFER_MCHUNK_END) {
		if (BB_BUFFER_MCHUNK_IS_VALID(mchunk)) {
			if (!mchunk->used) {
				if (aligned_size == mchunk->curr_size) {
					mchunk->used = 1;
					gkt_mutex_unlock(s_bb_buffer.mutex);

					return (uint8_t *)(mchunk + 1);
				}
				else if ((mchunk->curr_size > aligned_size)
					&& (!mchunk_suitable || (mchunk->curr_size < mchunk_suitable->curr_size))) 
				{
					mchunk_suitable = mchunk;
				}
			}

			mchunk = BB_BUFFER_MCHUNK_NEXT(mchunk);
		}
		else {
			gkt_mutex_unlock(s_bb_buffer.mutex);
			gkt_error("bb_buffer_alloc: memory is polluted!\n");
			return NULL;
		}
	}

	if (mchunk_suitable) {
		if ((aligned_size + BB_BUFFER_MCHUNK_MINSIZE) <= mchunk_suitable->curr_size) {
			mchunk = (bb_buffer_mchunk_s *)((uint8_t *)mchunk_suitable + BB_BUFFER_MCHUNK_HDRSIZE + aligned_size);
			mchunk->magic = BB_BUFFER_MCHUNK_MAGIC;
			mchunk->prev_size = aligned_size;
			mchunk->curr_size = mchunk_suitable->curr_size - aligned_size - BB_BUFFER_MCHUNK_HDRSIZE;
			mchunk->used = 0;
			bb_buffer_update_next_mchunk(mchunk);

			mchunk_suitable->curr_size = aligned_size;
		}
		mchunk_suitable->used = 1;
		gkt_mutex_unlock(s_bb_buffer.mutex);

		return (uint8_t *)(mchunk_suitable + 1);
	}

	gkt_mutex_unlock(s_bb_buffer.mutex);
	gkt_info("bb_buffer_alloc: no memory - size(%u)\n", size);

	return NULL;
}

int bb_buffer_init(void)
{
	bb_buffer_mchunk_s *mchunk;

	s_bb_buffer.mutex = gkt_mutex_create(0);
	if (!s_bb_buffer.mutex) {
		gkt_error("bb_buffer_init: create mutex failed!\n");
		return GKT_ECREATE;
	}

	memset(s_bb_buffer.data, 0, BB_BUFFER_MAXSIZE);
	mchunk = (bb_buffer_mchunk_s *)s_bb_buffer.data;
	mchunk->magic = BB_BUFFER_MCHUNK_MAGIC;
	mchunk->curr_size = BB_BUFFER_MAXSIZE - BB_BUFFER_MCHUNK_HDRSIZE;

	s_bb_buffer.init_ok = 1;

	return GKT_SUCCESS;
}

#endif

